package core

import (
	"reflect"
	"time"
	"math/big"
	"kumachan/standalone/ctn"
)


func ToObject[T any] (v T) Object {
	return MakeObject(v, reflect.ValueOf(&v).Type().Elem())
}
func MakeObject(v interface{}, typeHint reflect.Type) Object {
	if v == nil {
		v = Object(nil)
	}
	if o, is_object := v.(Object); is_object {
		return o
	}
	var o = (func() ObjectImpl {
	switch V := v.(type) {
	// Primitive
		// Bool
			case bool:
				return Bool(V)
		// Int
			case *big.Int:
				return Int { V }
			case int:
				return Int { big.NewInt(int64(V)) }
		// Float
			case float64:
				return Float(V)
		// Bytes
			case [] byte:
				return Bytes(V)
		// String
			case string:
				return String(V)
		// Char
			case int32:
				return Char(V)
		// Time
			case time.Time:
				return Time(V)
		// Error
			case error:
				return Error { V }
	// Rx
		// Observable
			case Observable:
				return V
	// Interface and Lambda (Object)
		// Interface
			case Interface:
				return V
		// Lambda (Object)
			case Lambda:
				return V
	// Container (Object)
		// List (Object)
			case List:
				return V
		// Queue
			case Queue:
				return V
		// Heap
			case Heap:
				return V
		// Set
			case Set:
				return V
		// Map
			case Map:
				return V
	// check for mistake
	case reflect.Value:
		panic("invalid argument")
	default:
		var rv = reflect.ValueOf(v)
		var rt = rv.Type()
		// Enum (int)
			if rt.Kind() == reflect.Int {
				var v = rv.Convert(reflect.TypeOf(int(0))).Interface().(int)
				return Enum(v)
			}
		// Union (ctn.Optional)
			if inner_rt, ok := ctn.ReflectTypeMatchOptional(rt); ok {
				if inner_rv, ok := ctn.ReflectOptionalValue(rv); ok {
					var inner_v = inner_rv.Interface()
					return Union {
						Index:  OkIndex,
						Object: MakeObject(inner_v, inner_rt),
					}
				} else {
					return Union { Index: NoneAndErrorIndex }
				}
			}
		// Union (interface)
			if typeHint.Kind() == reflect.Interface {
				var info = getUnionInterfaceInteropInfo(typeHint)
				return Union {
					Index:  info.getIndex(rt),
					Object: MakeObject(v, nil),
				}
			}
		// Record (ctn.Pair)
			if a_rt, b_rt, ok := ctn.ReflectTypeMatchPair(rt); ok {
				var a_rv, b_rv = ctn.ReflectPairDestruct(rv)
				var a_v = a_rv.Interface()
				var b_v = b_rv.Interface()
				var a = MakeObject(a_v, a_rt)
				var b = MakeObject(b_v, b_rt)
				return Record {
					Objects: [] Object { a, b },
				}
			}
		// Record (struct)
			if rt.Kind() == reflect.Struct {
				var n = rt.NumField()
				var objects = make([] Object, n)
				for i := 0; i < n; i += 1 {
					var field_rt = rt.Field(i).Type
					var field_rv = rv.Field(i)
					var field_v = field_rv.Interface()
					objects[i] = MakeObject(field_v, field_rt)
				}
				return Record { objects }
			}
	// Container (generic)
		// List (generic)
			if rt.Kind() == reflect.Slice {
				var item_rt = rt.Elem()
				var n = rv.Len()
				var nodes = make([] ListNode, n)
				for i := 0; i < n; i += 1 {
					var item_rv = rv.Index(i)
					var item_v = item_rv.Interface()
					nodes[i].Value = MakeObject(item_v, item_rt)
				}
				return NodesToList(nodes)
			}
	// Interface and Lambda (generic)
		// Lambda (generic)
			if isLambdaFuncReflectType(rt) {
				var get_in_rv func(Object)([] reflect.Value)
				if rt.NumIn() == 0 {
					get_in_rv = func(_ Object) ([] reflect.Value) {
						return nil
					}
				} else if rt.NumIn() == 1 {
					get_in_rv = func(arg Object) ([] reflect.Value) {
						var arg_ptr, arg_rv = reflectNew(rt.In(0))
						ConvertObject(arg, arg_ptr)
						return [] reflect.Value { arg_rv }
					}
				} else if rt.NumIn() == 2 {
					get_in_rv = func(arg Object) ([] reflect.Value) {
						var a_ptr, a_rv = reflectNew(rt.In(0))
						var b_ptr, b_rv = reflectNew(rt.In(1))
						var r = (*arg).(Record) // ctn.Pair
						ConvertObject(r.Objects[0], a_ptr)
						ConvertObject(r.Objects[1], b_ptr)
						return [] reflect.Value { a_rv, b_rv }
					}
				} else {
					panic("something went wrong")
				}
				var out_rt = rt.Out(0)
				var call = func(arg Object) Object {
					var in_rv = get_in_rv(arg)
					var out_rv = rv.Call(in_rv)
					var out = out_rv[0].Interface()
					return MakeObject(out, out_rt)
				}
				return Lambda {
					Call: call,
				}
			}
		panic("unsupported value type")
	} })()
	return &o
}

func FromObject[T any] (o Object) T {
	var t T
	var p = &t
	ConvertObject(o, p)
	return t
}
func ConvertObject(o Object, p interface{}) {
	if o == nil {
		return
	}
	if o_ptr, is_object_ptr := p.(*Object); is_object_ptr {
		*o_ptr = o
		return
	}
	{ var o = *o
	switch P := p.(type) {
	// Primitive
		// Bool
			case *(bool):
				*P = bool(o.(Bool))
		// Int
			case *(*big.Int):
				*P = o.(Int).Value
			case *(int):
				// note: undefined value returned when overflow
				*P = int(o.(Int).Value.Int64())
		// Float
			case *(float64):
				*P = float64(o.(Float))
		// Bytes
			case *([] byte):
				*P = o.(Bytes)
		// String
			case *(string):
				*P = string(o.(String))
		// Char
			case *(int32):
				*P = int32(o.(Char))
		// Time
			case *(time.Time):
				*P = time.Time(o.(Time))
		// Error
			case *(error):
				*P = o.(Error).Value
	// Rx
		// Observable
			case *(Observable):
				*P = o.(Observable)
	// Interface and Lambda (Object)
		// Interface
			case *(Interface):
				*P = o.(Interface)
		// Lambda (Object)
			case *(Lambda):
				*P = o.(Lambda)
	// Container (Object)
		// List (Object)
			case *(List):
				*P = o.(List)
		// Queue
			case *(Queue):
				*P = o.(Queue)
		// Heap
			case *(Heap):
				*P = o.(Heap)
		// Set
			case *(Set):
				*P = o.(Set)
		// Map
			case *(Map):
				*P = o.(Map)
	default:
		var ptr_rv = reflect.ValueOf(p)
		// check for mistake
		if ptr_rv.Kind() != reflect.Ptr {
			panic("invalid argument")
		}
		var rv = ptr_rv.Elem()
		var rt = rv.Type()
		// Enum (int)
			if rt.Kind() == reflect.Int {
				var v = int(o.(Enum))
				rv.Set(reflect.ValueOf(v).Convert(rt))
				return
			}
		// Union (ctn.Optional)
			if inner_rt, ok := ctn.ReflectTypeMatchOptional(rt); ok {
				var u = o.(Union)
				if u.Index == OkIndex {
					var inner_ptr, inner_rv = reflectNew(inner_rt)
					ConvertObject(u.Object, inner_ptr)
					rv.Set(ctn.ReflectJust(inner_rv))
				} else if u.Index == NoneAndErrorIndex {
					rv.Set(ctn.ReflectNull(inner_rt))
				} else {
					panic("something went wrong")
				}
				return
			}
		// Union (interface)
			if rt.Kind() == reflect.Interface {
				var u = o.(Union)
				var info = getUnionInterfaceInteropInfo(rt)
				var inner_ptr, inner_rv = reflectNew(info.getType(u.Index))
				ConvertObject(u.Object, inner_ptr)
				rv.Set(inner_rv)
				return
			}
		// Record (ctn.Pair)
			if a_rt, b_rt, ok := ctn.ReflectTypeMatchPair(rt); ok {
				var r = o.(Record)
				var a_ptr, a_rv = reflectNew(a_rt)
				var b_ptr, b_rv = reflectNew(b_rt)
				ConvertObject(r.Objects[0], a_ptr)
				ConvertObject(r.Objects[1], b_ptr)
				rv.Set(ctn.ReflectMakePair(a_rv, b_rv))
				return
			}
		// Record (struct)
			if rt.Kind() == reflect.Struct {
				var r = o.(Record)
				var n = len(r.Objects)
				for i := 0; i < n; i += 1 {
					var field_ptr_rv = rv.Field(i).Addr()
					var field_ptr = field_ptr_rv.Interface()
					ConvertObject(r.Objects[i], field_ptr)
				}
				return
			}
	// Container (generic)
		// List (generic)
			if rt.Kind() == reflect.Slice {
				var l = o.(List)
				var slice_rv = reflect.MakeSlice(rt, 0, 0)
				l.ForEach(func(el_obj Object) {
					var el_ptr_rv = reflect.New(rt.Elem())
					var el_ptr = el_ptr_rv.Interface()
					ConvertObject(el_obj, el_ptr)
					var el_rv = el_ptr_rv.Elem()
					slice_rv = reflect.Append(slice_rv, el_rv)
				})
				rv.Set(slice_rv)
				return
			}
	// Interface and Lambda (generic)
		// Lambda (generic)
			if isLambdaFuncReflectType(rt) {
				var l = o.(Lambda)
				var get_arg_obj func([] reflect.Value) Object
				if rt.NumIn() == 0 {
					get_arg_obj = func(_ ([] reflect.Value)) Object {
						return nil
					}
				} else if rt.NumIn() == 1 {
					get_arg_obj = func(in ([] reflect.Value)) Object {
						var in0_v = in[0].Interface()
						return MakeObject(in0_v, rt.In(0))
					}
				} else if rt.NumIn() == 2 {
					get_arg_obj = func(in ([] reflect.Value)) Object {
						var in0_v = in[0].Interface()
						var in1_v = in[1].Interface()
						var a = MakeObject(in0_v, rt.In(0))
						var b = MakeObject(in1_v, rt.In(1))
						var pair = [] Object { a, b }
						var o = ObjectImpl(Record { pair })
						return &o
					}
				} else {
					panic("something went wrong")
				}
				var out_rt = rt.Out(0)
				var f_rv = reflect.MakeFunc(rt, func(in ([] reflect.Value)) ([] reflect.Value) {
					var arg = get_arg_obj(in)
					var ret = l.Call(arg)
					var out_ptr, out_rv = reflectNew(out_rt)
					ConvertObject(ret, out_ptr)
					var out = [] reflect.Value { out_rv }
					return out
				})
				rv.Set(f_rv)
				return
			}
		panic("unsupported pointer type")
	} }
}

func MakeNativeFunction(v interface{}) NativeFunction {
	var rv = reflect.ValueOf(v)
	var rt = rv.Type()
	if !((rt.Kind() == reflect.Func) && !(rt.IsVariadic())) {
		panic("invalid argument")
	}
	return NativeFunction(func(args ([] Object), ctx ([] Object), h RuntimeHandle) Object {
		var num_in = rt.NumIn()
		var num_out = rt.NumOut()
		var in = make([] reflect.Value, num_in)
		for i := 0; i < num_in; i += 1 {
			var in_t = rt.In(i)
			if i < len(args) {
				var arg = args[i]
				var arg_ptr, arg_rv = reflectNew(in_t)
				ConvertObject(arg, arg_ptr)
				in[i] = arg_rv
			} else {
				var j = (i - len(args))
				if j < len(ctx) {
					var item = ctx[j]
					var item_ptr, item_rv = reflectNew(in_t)
					ConvertObject(item, item_ptr)
					in[i] = item_rv
				} else {
					in[i] = reflect.ValueOf(h)
				}
			}
		}
		var out = rv.Call(in)
		if num_out == 0 {
			return nil
		} else if num_out == 1 {
			var out_v = out[0].Interface()
			return MakeObject(out_v, rt.Out(0))
		} else {
			panic("invalid argument")
		}
	})
}

const unionInterfaceInteropDummyMethod = "Union"
type unionInterfaceInteropInfo struct
	{ dummyStruct reflect.Type }
func getUnionInterfaceInteropInfo(i reflect.Type) unionInterfaceInteropInfo {
	var m, ok = i.MethodByName(unionInterfaceInteropDummyMethod)
	if !(ok) { panic("missing union interface dummy method") }
	return unionInterfaceInteropInfo { m.Type.Out(0) }
}
func (info unionInterfaceInteropInfo) getIndex(t reflect.Type) int {
	var field, ok = info.dummyStruct.FieldByName(t.Name())
	if !(ok) { panic("something went wrong") }
	return field.Index[0]
}
func (info unionInterfaceInteropInfo) getType(index int) reflect.Type {
	return info.dummyStruct.Field(index).Type
}

func reflectNew(t reflect.Type) (interface{}, reflect.Value) {
	var ptr_rv = reflect.New(t)
	var ptr = ptr_rv.Interface()
	var rv = ptr_rv.Elem()
	return ptr, rv
}
func isLambdaFuncReflectType(t reflect.Type) bool {
	return (t.Kind() == reflect.Func) &&
		!(t.IsVariadic()) &&
		(t.NumIn() == 0 || t.NumIn() == 1 || t.NumIn() == 2) &&
		(t.NumOut() == 1)
}


